#ifdef _WIN64
#include <windows.h>
#endif
#include "Logging.hxx"
#include "Sequence.hxx"
#include "VideoThread.hxx"

#define SIZE_FACTOR		1.0

VideoThread::VideoThread()
{
    pFormatCtx = 0;
    videoindex = -1;
    pCodecCtx = 0;
    pCodec = 0;
	//_stopFlag = false;
	//_pauseFlag = false;
	_videoStatus = VT_STATUS_UNKOWN;
	_videoFrameBuffer = 0;
	//_pauseOnFrameFlag = false;
	_needOrderCheck = false;
}

VideoThread::~VideoThread()
{
	if (_videoFrameBuffer)
	{
		delete[] _videoFrameBuffer;
		_videoFrameBuffer = 0;
	}
}


void VideoThread::run()
{
	decodeVideo();
}

void VideoThread::initialize()
{
    pFormatCtx = avformat_alloc_context();

    QString videoFileName = _model->getFilePath().c_str();

	SPDLOG_DEBUG("Ready to open the video file {}", _model->getFilePath().c_str());

    // Open file and put the file header info into AVFormatContext;
    if (avformat_open_input(&pFormatCtx, videoFileName.toLatin1(), NULL, NULL) != 0)
    {
		SPDLOG_DEBUG("Couldn't open input stream.\n");
        return;
    }

    // Find file stream;
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
    {
		SPDLOG_DEBUG("Couldn't find stream information.\n");
        return;
    }

	if (pFormatCtx->duration != AV_NOPTS_VALUE) {
		int hours, mins, secs, us;
		int64_t duration = pFormatCtx->duration + 5000;
		secs = duration / AV_TIME_BASE;
		us = duration % AV_TIME_BASE;//1000000
		mins = secs / 60;
		secs %= 60;
		hours = mins / 60;
		mins %= 60;
		printf("%02d:%02d:%02d.%02d\n", hours, mins, secs, (100 * us) / AV_TIME_BASE);
	}

    //Iterative streams and locate the first video stream
    videoindex = -1;
    for (int i = 0; i < pFormatCtx->nb_streams; i++)
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoindex = i;
            break;
        }

    if (videoindex == -1) {
        printf("Didn't find a video stream.\n");
        return;
    }

    pCodecCtx = pFormatCtx->streams[videoindex]->codec;

	//if (pCodecCtx->codec_id == AV_CODEC_ID_H264)
	//	pCodec = avcodec_find_decoder_by_name("h264_cuvid");

	//if( !pCodec )
	//	pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
	pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

    av_read_play(pFormatCtx);

    if (pCodec == NULL) {
        printf("Codec not found.\n");
		SPDLOG_DEBUG("Could not found video codec Id.");
        return;
    }

	SPDLOG_DEBUG("Found the video codec.");

    pCodecCtx = avcodec_alloc_context3(pCodec);
    avcodec_get_context_defaults3(pCodecCtx, pCodec);
    avcodec_copy_context(pCodecCtx, pFormatCtx->streams[videoindex]->codec);

    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        printf("Could not open codec.\n");
		SPDLOG_DEBUG("Could not open video codec.");
        return;
    }

	int64_t duration = pFormatCtx->duration;

	int vWidth = pCodecCtx->width * SIZE_FACTOR;
	int vHeight = pCodecCtx->height * SIZE_FACTOR;

    _model->setVideoWidth(vWidth);
	_model->setVideoHeight(vHeight);
	_model->setVideoDuration(duration / AV_TIME_BASE);
	_model->setFrameRate(av_q2d(pFormatCtx->streams[videoindex]->r_frame_rate));

	SPDLOG_DEBUG("Video Properties:");
	SPDLOG_DEBUG("\tWidth:{}", vWidth);
	SPDLOG_DEBUG("\tHeight:{}", vHeight);

	_videoFrameInfo.Width = vWidth;
	_videoFrameInfo.Height = vHeight;	
   
	_framePool->setPageSize(sizeof(VideoFrameInfo) + vWidth * vHeight * 3);
    _framePool->initialize();

	_videoFrameBuffer = new char[sizeof(VideoFrameInfo) + vWidth * vHeight * 3];
}

#define SSTR( x ) static_cast< std::ostringstream & >( \
( std::ostringstream() << std::dec << x ) ).str()

void VideoThread::decodeVideo()
{
	/////////////////////////////////////////////////////////////////
	/// // 分配空间存储图像数据
	AVFrame *frame = av_frame_alloc();
	AVPicture picture{};
	avpicture_alloc(&picture, AV_PIX_FMT_BGR24, pCodecCtx->width * SIZE_FACTOR, pCodecCtx->height * SIZE_FACTOR);

	int unfinished;
	AVPacket packet{};
	struct SwsContext * img_convert_ctx = nullptr;

	bool videoPlayingFlag = true;
	while ( videoPlayingFlag )
	{
		msleep(8);

		if (_videoStatus == VT_STATUS_STOPPED)
			break;

		if (_videoStatus == VT_STATUS_PAUSED)
		{
			msleep(20);
			continue;
		}
		//else
		//{
		//	_videoStatus = VT_STATUS_PLAYING;
		//}

		videoPlayingFlag = av_read_frame(pFormatCtx, &packet) >= 0;

		if (packet.stream_index == videoindex)
		{
			_model->lockSeekAction();
			double seekpos = _model->getSeekPos();
			bool seekFlag = _model->getSeekFlag();
			if (seekFlag)
			{
				//int64 seekTarget = seekpos * AV_TIME_BASE;
				int64 seekTarget = seekpos;

				AVRational rational;
				rational.num = 1;
				rational.den = pFormatCtx->streams[videoindex]->r_frame_rate.num;

				seekTarget = av_rescale_q(seekTarget,
					rational,
					pFormatCtx->streams[videoindex]->time_base);

				seekTarget += pFormatCtx->streams[videoindex]->start_time;

				av_seek_frame(pFormatCtx, videoindex,seekTarget, AVSEEK_FLAG_BACKWARD);

				avcodec_flush_buffers(pCodecCtx);

				_model->setSeekFlag();
			}
			_model->unlockSeekAction();


			// Decode the video frame: packet => frame
			avcodec_decode_video2(pCodecCtx, frame, &unfinished, &packet);

			if (unfinished)
			{
				// Convert to BGR24.
				img_convert_ctx = sws_getCachedContext(
					img_convert_ctx,
					pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
					pCodecCtx->width * SIZE_FACTOR, pCodecCtx->height * SIZE_FACTOR, AV_PIX_FMT_BGR24,
					SWS_BICUBIC, nullptr, nullptr, nullptr);

				sws_scale(img_convert_ctx, reinterpret_cast<const uint8_t *const *>(frame->data), frame->linesize,
					0, pCodecCtx->height,
					picture.data, picture.linesize);

				int64 xpts = frame->pts;

				double currentTime = (xpts - pFormatCtx->streams[videoindex]->start_time) * av_q2d(pFormatCtx->streams[videoindex]->time_base);

				// Use OpenCV to display the picture.
				VideoFrameInfo* vfi = (VideoFrameInfo*)_videoFrameBuffer;

				int ptsStep = pFormatCtx->streams[videoindex]->time_base.den / av_q2d(pFormatCtx->streams[videoindex]->r_frame_rate);
				int findex = (xpts - pFormatCtx->streams[videoindex]->start_time) * 1.0 / ptsStep + 0.5;
				vfi->Width = _videoFrameInfo.Width;
				vfi->Height = _videoFrameInfo.Height;
				vfi->FrameIndex = findex;
				vfi->TimeStamp = currentTime;

				if (findex % _model->getFrameStrip() != 0)
					continue;

				memcpy((char*)_videoFrameBuffer + sizeof(VideoFrameInfo), (const void*)(picture.data[0]), _videoFrameInfo.Width * _videoFrameInfo.Height * 3);

				while (_framePool->isFull())
				{
					if (_videoStatus == VT_STATUS_STOPPED)
						break;

					msleep(30);
				}

				//if (!doOrderCheck(findex))
				//	continue;

				while (!_framePool->pushFrame((const char*)_videoFrameBuffer))
				{

					if (_videoStatus == VT_STATUS_STOPPED)
						break;

					msleep(10);
				}

				if (_videoStatus == VT_STATUS_GOFRAME)
				{
					if (_targetFrameIndex == findex)
					{
						_videoStatus = VT_STATUS_PAUSED;
						//_pauseFlag = true;
						//_stopFlag = false;
						//_pauseOnFrameFlag = false;
					}
				}	

				if (_videoStatus == VT_STATUS_STEPPING)
				{
					_videoStatus = VT_STATUS_PAUSED;
				}
			}
			av_free_packet(&packet);
		}
	}

	sws_freeContext(img_convert_ctx);
	av_free_packet(&packet);
	avcodec_close(pCodecCtx);
	av_free(frame);
	avpicture_free(&picture);
	avformat_close_input(&pFormatCtx);

	_videoStatus = VT_STATUS_FINISHED;
	//_stopFlag = true;
}

void VideoThread::actionGoFrame(int frameIndex)
{
	_targetFrameIndex = frameIndex;
	//_stopFlag = false;
	//_pauseFlag = false;
	//_pauseOnFrameFlag = true;
	_videoStatus = VT_STATUS_GOFRAME;

	disableOrderCheck();
}

void VideoThread::enableOrderCheck()
{
	_needOrderCheck = true;
}

void VideoThread::disableOrderCheck()
{
	_needOrderCheck = false;
}

bool VideoThread::doOrderCheck(const int findex)
{
	static int lastFrameIndex = findex;

	bool ret = true;

	if (_needOrderCheck)
	{
		if ((findex < lastFrameIndex) || (findex - lastFrameIndex != 1))
			ret = false;
	}
	else
	{
		enableOrderCheck();
	}

	lastFrameIndex = findex;

	return ret;
}
